package pe;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.GZIPInputStream;

public final class LoadGenerator
{

    private static String myURL = "http://131.94.128.116:8087/_index.html";
//    private static final String myURL = "http://localhost:8087/_index.html";
    private static final int TOTAL_REQUEST_COUNT = 100000;
    private int CONCURRENT_REQUEST_COUNT = 1;
    private final ScheduledExecutorService alarmScheduler;
    private final long initialDelay = 5;
    private final long delayBetweenRuns = 10;
    private final long shutdownAfter = 600000;
    private static final boolean DONT_INTERRUPT_IF_RUNNING = false;
    private AtomicInteger requestCount = new AtomicInteger(0);
    private AtomicInteger prevRequestCount = new AtomicInteger(0);
    private int throughput = 0;
    public static final String MIME_PLAINTEXT = "text/plain";

    public LoadGenerator(int conRequestCount)
    {
        CONCURRENT_REQUEST_COUNT = conRequestCount;
        alarmScheduler = Executors.newScheduledThreadPool(1);
        activateAlarmThenStop();
    }

    /**
     * Hold all the date related to a ping.
     */
    private final class RequestResult
    {

        String URL;
        Boolean SUCCESS;
        Long TIMING;

        @Override
        public String toString()
        {
            return "Result:" + SUCCESS + " " + TIMING + " ms " + URL;
        }
    }

    /**
     * Try to ping a URL. Return true only if successful.
     */
    private final class Request implements Callable<RequestResult>
    {

        private final String fURL;

        Request(String aURL)
        {
            fURL = aURL;
        }

        public RequestResult call() throws Exception
        {
            requestCount.incrementAndGet();
            return requestAndReportStatus(fURL);
        }
    }

    private RequestResult requestAndReportStatus(String aURL) throws MalformedURLException
    {
        RequestResult result = new RequestResult();
        result.URL = aURL;
        long start = System.currentTimeMillis();
        try
        {
            requestToServer(aURL);
            result.SUCCESS = true;
            long end = System.currentTimeMillis();
            result.TIMING = end - start;
        } catch (IOException ex)
        {
            ex.printStackTrace();
        }
        return result;
    }

    public String requestToServer(String urlToRead) throws IOException
    {
        URL url;
        HttpURLConnection conn;
        String result = "";
        url = new URL(urlToRead);
        conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");

        if (!conn.getHeaderField("Content-Type").equals(MIME_PLAINTEXT))
        {
            final InputStream inputStream = conn.getInputStream();

//            BufferedReader rd;
//            String line;
//            rd = new BufferedReader(new InputStreamReader(inputStream));
//            while ((line = rd.readLine()) != null)
//            {
//                result += line;
//                System.out.println(line);
//            }
//            rd.close();

            GZIPInputStream zip = new GZIPInputStream(inputStream);
            while (true)
            {
                int c;
                c = zip.read();
                if (c == -1)
                {
                    break;
                }
//                System.out.print((char) c);
            }

        } else
        {
            System.out.println("File Not Found");
        }

        conn.disconnect();
        return result;
    }

    /**
     * Check N sites, in parallel, using up to 4 threads. Report the result of
     * each 'ping' as it comes in. (This is likely the style most would prefer.)
     */
    void requestAndReportEachWhenKnown() throws InterruptedException, ExecutionException
    {
        ExecutorService executorService = Executors.newFixedThreadPool(CONCURRENT_REQUEST_COUNT);
        CompletionService<RequestResult> completionService =
                new ExecutorCompletionService<RequestResult>(executorService);

        long start = System.currentTimeMillis();
        for (int i = 0; i < TOTAL_REQUEST_COUNT; i++)
        {
            Request task = new Request(myURL);
            completionService.submit(task);
        }

        Future<RequestResult> future = completionService.take();
        log("Response Time: " + future.get());

        for (int idx = 0; idx < TOTAL_REQUEST_COUNT - 1; ++idx)
        {
            future = completionService.take();
            future.get();
//            log(future.get());
        }

        executorService.shutdown(); //always reclaim resources
        long end = System.currentTimeMillis();
        System.out.println("Total time: " + (end - start));
    }

    /**
     * Check N sites, in parallel, using up to 4 threads. Report the results
     * only when all have completed.
     */
    void requestAndReportAllAtEnd() throws InterruptedException, ExecutionException
    {
        Collection<Callable<RequestResult>> tasks = new ArrayList<Callable<RequestResult>>();
        for (int i = 0; i < TOTAL_REQUEST_COUNT; i++)
        {
            tasks.add(new Request(myURL));
        }
        ExecutorService executor = Executors.newFixedThreadPool(CONCURRENT_REQUEST_COUNT);
        long start = System.currentTimeMillis();
        List<Future<RequestResult>> results = executor.invokeAll(tasks);
        for (Future<RequestResult> result : results)
        {
            RequestResult pingResult = result.get();
            log(pingResult);
        }
        executor.shutdown(); //always reclaim resources
        long end = System.currentTimeMillis();
        System.out.println("Total time: " + (end - start));
    }

    /**
     * Check N sites, but sequentially, not in parallel. Does not use multiple
     * threads at all.
     */
    void requestAndReportSequentially() throws MalformedURLException
    {
        for (int i = 0; i < TOTAL_REQUEST_COUNT; i++)
        {
            RequestResult requestResult = requestAndReportStatus(myURL);
            log(requestResult);
        }
    }

    public void activateAlarmThenStop()
    {
        Runnable startAlarmTask = new LoadGenerator.StartAlarmTask();
        ScheduledFuture<?> soundAlarmFuture = alarmScheduler.scheduleWithFixedDelay(
                startAlarmTask, initialDelay, delayBetweenRuns, TimeUnit.SECONDS);
        Runnable stopAlarmTask = new LoadGenerator.StopAlarmTask(soundAlarmFuture);
        alarmScheduler.schedule(stopAlarmTask, shutdownAfter, TimeUnit.SECONDS);
    }

    public final class StartAlarmTask implements Runnable
    {

        public void run()
        {
            final int totalRequestCount = requestCount.get() - prevRequestCount.get();
            if (throughput < totalRequestCount)
            {
                throughput = totalRequestCount;
            }

            if (totalRequestCount > 0)
            {
                log("Concurrent Request Count: " + CONCURRENT_REQUEST_COUNT);
                log("Request Served Over " + delayBetweenRuns + "s: " + totalRequestCount);
                log("Throughput: " + throughput);
            }

            System.out.println();

            prevRequestCount.set(requestCount.get());
        }
    }

    public final class StopAlarmTask implements Runnable
    {

        StopAlarmTask(ScheduledFuture<?> aSchedFuture)
        {
            fSchedFuture = aSchedFuture;
        }

        public void run()
        {
            log("Stopping alarm.");
            fSchedFuture.cancel(DONT_INTERRUPT_IF_RUNNING);

            /*
             * Note that this Task also performs cleanup, by asking the
             * scheduler to shutdown gracefully.
             */
            alarmScheduler.shutdown();
        }
        private ScheduledFuture<?> fSchedFuture;
    }

    private static void log(Object aMsg)
    {
        System.out.println(String.valueOf(aMsg));
    }

    public static void main(String[] args)
    {
        int conRequestCount = 1;
        for (int i = 0; i < args.length; i++)
        {
            if (args[i].equalsIgnoreCase("-n"))
            {
                conRequestCount = Integer.parseInt(args[i + 1]);
            }
            if (args[i].equalsIgnoreCase("-u"))
            {
                myURL = args[i + 1];
            }
        }

        LoadGenerator generator = new LoadGenerator(conRequestCount);
        try
        {
            log("Parallel, report each as it completes:");
            generator.requestAndReportEachWhenKnown();

//            log("Parallel, report all at end:");
//            generator.requestAndReportAllAtEnd();

//            log("Sequential, report each as it completes:");
//            generator.requestAndReportSequentially();

        } catch (InterruptedException ex)
        {
            Thread.currentThread().interrupt();
        } catch (ExecutionException ex)
        {
            log("Problem executing worker: " + ex.getCause());
        }
//        catch (MalformedURLException ex)
//        {
//            log("Bad URL: " + ex.getCause());
//        }
        log("Done.");
    }
}
